#include <iostream>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <signal.h>

using namespace std;

//我们之前一直说,信号是在合适的时候进行处理,那么什么时候适合的?
//从内核态转换为用户态的时候,就是信号的检测与信号的处理
//内核态:执行OS的代码和数据的时候,计算机所处的状态就叫做内核态,OS的代码执行全部都是在内核态执行的
//用户态:就是用户代码和数据被访问或者执行的时候,所处的状态,我们自己写的代码全部都是在用户态执行的
//它们的主要区别:在于权限

//我们回到之前的进程地址空间
//每个进程都有自己的地址空间
//其中0~3g是属于用户空间,而3~4g是属于内核空间
//那么0~3g用的是用户级页表,将用户的代码和数据映射到内存当中
//而3~4g用的是系统级页表,将系统的代码和数据映射到内存当中
//一个进程为了保证其独立性,其用户级页表是独立的,不同的进程所映射到内存中的地址是不一样的,而内核只有一个,所以不同的进程,用的内核级页表是同一张

//进程具有了地址空间是能够看到用户和内核的所有内容的,但是因为身份的原因
//是不能进行访问的
//而CPU中有一个CR3寄存器,保存了当前进程的状态
//用户态使用的是,用户级页表,只能访问用户的数据和代码
//内核态使用的是,内核级页表,只能访问内核级的代码和数据


//如果我们拿到了内核代码和数据的地址,以用户态的形式可以进行内核的访问吗?
//因为CR3的存在,进行内核级页表的访问的时候,会检查我们的身份,也是不能直接进行访问的

//当我们正常执行我们自己写的代码的时候,进程是处于用户态的
//如果我们执行到了系统调用,那么我们的身份转化为内核,访问内核的数据和代码,此时进程是处于内核态
//执行完成内核的代码后,但会用户态,继续执行用户的代码

//如果不进行系统调用,我们是不是就没办法进入内核态?
//也不是的,当我们进程运行的时间片到了的时候,我们就会从用户态转化为内核态
//执行内核里面进程切换的代码,然后转换到用户态,继续被切换过来的进程的代码

//进程之间无论怎么切换,我们一定能找到同一个操作系统,因为我们每个进程的3~4g的空间,使用的是同一张页表
//所谓的系统调用:就是进程的身份转化为内核,然后根据内核页表找到系统函数,执行
//在大部分情况下,我们的OS系统都是可以在进程的上下文中之间运行的

//下面我们详细说说信号的捕捉的全部过程
//1.在执行主控制流程的某条指令时,因为中断/异常/系统调用,或者是进程切换时进入内核
//2.内核处理完中断/异常/系统调用/进程切换,准备转换为用户态的时候,先处理当前进程中可以递达的信号
//3.这个时候,内核执行do_signal()函数
//如果信号的处理动作是默认的,忽略的,那么内核执行处理函数完毕后,返回用户态
//如果信号的处理动作是自定义的,处理函数则回到用户态执行信号处理函数
//(不是回到主控制流程, 这里也就解释了,为什么不能以内核态直接执行用户的代码,虽然理论上是可以的,但是OS不相信任何人,如果这个信号处理函数里面,有一些恶意代码,那么因为内核态的权限很高,是可以做出一些破坏的)
//4.信号处理函数执行完毕,返回时执行特殊的系统调用sigreturn再次进入内核
//5.如果还有信号需要处理,继续上面的处理动作
//如果没有信号需要处理,那么执行sys_sigreturn()函数,返回用户态,从主控制流程中上次被中断的地方继续向下执行

//sigaction
//功能:
//    可以读取和修改与指定信号相关联的处理动作
//原型:
//    int sigaction(int signo, const struct sigaction* act, struct sigaction* oact); //c语言中函数名和结构体的名字可以一样,c++中就是构造函数了
//参数:
//    signo是指定信号的编号
//    struct sigaction的具体结构如下:
//    struct sigaction 
//    {
//          void       (*sa_handler)(int);
//          void       (*sa_sigaction)(int, siginfo_t *, void *);
//          sigset_t   sa_mask;
//          int        sa_flags;
//          void       (*sa_restorer)(void);
//    };
//    其中第二个成员变量和第五个成员变量是实时信号的,我们不管
//    第一个成员变量是这个信号的函数处理方法
//    第三个成员变量是这个这个信号抵达时,需要屏蔽的信号
//    第四个成员变量包含了一些选型,如下所示:
//    SA_RESETHAND：当调用信号处理函数时，将信号的处理函数重置为缺省值SIG_DFL
//    SA_RESTART：如果信号中断了进程的某个系统调用，则系统自动启动该系统调用
//    SA_NODEFER ：一般情况下，当信号处理函数运行时，内核将阻塞该给定信号。但是如果设置了 SA_NODEFER标记，那么在该信号处理函数运行时，内核将不会阻塞该信号
//
//    如果act指针非空,则根据act修改该信号的处理动作
//    如果oact指针非空,则通过oact传出该信号原来的处理动作
//返回值:
//    成功返回0, 失败返回-1
//ps:
//    当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产生,那么 它会被阻塞到当前处理结束为止。


void handler(int signo)
{
    int count = 5;
    while(count--)
    {
        cout << "signo:" << signo << endl;
        sleep(1);
    }
    exit(0);
}

int main()
{
    struct sigaction act;
    memset(&act, 0, sizeof(act));

    //修改指定的函数处理方法
    act.sa_handler = handler;

    //初始化信号屏蔽字
    sigemptyset(&act.sa_mask);
    //修改信号屏蔽字
    sigaddset(&act.sa_mask, 3);

    //本质是修改当前进程的handler函数指针数组特性内容
    sigaction(2, &act, nullptr);

    while(1)
    {
        cout << "hello" << endl;
        sleep(1);
    }

    return 0;
}
